﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kokomo.Mirror.CliMetadata
{
	public struct CodedIndex : IEquatable<CodedIndex>
	{
		static CodedIndex()
		{
			MetadataTableNumber[][] codedIndexTypeTableNumbers = new MetadataTableNumber[14][];
			codedIndexTypeTableNumbers[(int)CodedIndexType.TypeDefOrRef] = new MetadataTableNumber[] {
				MetadataTableNumber.TypeDef,
				MetadataTableNumber.TypeRef,
				MetadataTableNumber.TypeSpec
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.HasConstant] = new MetadataTableNumber[] {
				MetadataTableNumber.Field,
				MetadataTableNumber.Param,
				MetadataTableNumber.Property
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.HasCustomAttribute] = new MetadataTableNumber[] {
				MetadataTableNumber.MethodDef,
				MetadataTableNumber.Field,
				MetadataTableNumber.TypeRef,
				MetadataTableNumber.TypeDef,
				MetadataTableNumber.Param,
				MetadataTableNumber.InterfaceImpl,
				MetadataTableNumber.MemberRef,
				MetadataTableNumber.Module,
				MetadataTableNumber.Param,
				MetadataTableNumber.Property,
				MetadataTableNumber.Event,
				MetadataTableNumber.StandAloneSig,
				MetadataTableNumber.ModuleRef,
				MetadataTableNumber.TypeSpec,
				MetadataTableNumber.Assembly,
				MetadataTableNumber.AssemblyRef,
				MetadataTableNumber.File,
				MetadataTableNumber.ExportedType,
				MetadataTableNumber.ManifestResource
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.HasFieldMarshal] = new MetadataTableNumber[] {
				MetadataTableNumber.Field,
				MetadataTableNumber.Param
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.HasDeclSecurity] = new MetadataTableNumber[] {
				MetadataTableNumber.TypeDef,
				MetadataTableNumber.MethodDef,
				MetadataTableNumber.Assembly
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.MemberRefParent] = new MetadataTableNumber[] {
				MetadataTableNumber.TypeDef,
				MetadataTableNumber.TypeRef,
				MetadataTableNumber.ModuleRef,
				MetadataTableNumber.MethodDef,
				MetadataTableNumber.TypeSpec
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.HasSemantics] = new MetadataTableNumber[] {
				MetadataTableNumber.Event,
				MetadataTableNumber.Property
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.MethodDefOrRef] = new MetadataTableNumber[] {
				MetadataTableNumber.MethodDef,
				MetadataTableNumber.MethodRef
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.MemberForwarded] = new MetadataTableNumber[] {
				MetadataTableNumber.Field,
				MetadataTableNumber.MethodDef
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.Implementation] = new MetadataTableNumber[] {
				MetadataTableNumber.File,
				MetadataTableNumber.AssemblyRef,
				MetadataTableNumber.ExportedType
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.CustomAttributeType] = new MetadataTableNumber[] {
				MetadataTableNumber.Invalid,
				MetadataTableNumber.Invalid,
				MetadataTableNumber.MethodDef,
				MetadataTableNumber.MemberRef,
				MetadataTableNumber.Invalid
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.ResolutionScope] = new MetadataTableNumber[] {
				MetadataTableNumber.Module,
				MetadataTableNumber.ModuleRef,
				MetadataTableNumber.AssemblyRef,
				MetadataTableNumber.TypeRef
			};
			codedIndexTypeTableNumbers[(int)CodedIndexType.TypeOrMethodDef] = new MetadataTableNumber[] {
				MetadataTableNumber.TypeDef,
				MetadataTableNumber.MethodDef
			};

			_codedIndexTypeTableNumbers = codedIndexTypeTableNumbers;
		}

		private static MetadataTableNumber[][] _codedIndexTypeTableNumbers;

		public const int NullMethodDef = 0x06000000;
		public const int NullTypeDef = 0x02000000;

		public static int Log2(int maxValue)
		{
			int size = 0;
			while (maxValue != 0)
			{
				size++;
				maxValue >>= 1;
			}

			return size;
		}

		public static MetadataTableNumber[] GetTableNumbers(CodedIndexType indexType)
		{
			return _codedIndexTypeTableNumbers[(int)indexType];
		}
		public static MetadataTableNumber ResolveSelector(
			CodedIndexType indexType,
			int selector
			)
		{
			MetadataTableNumber[] tableNumbers = GetTableNumbers(indexType);
			if (selector >= tableNumbers.Length)
				throw new ArgumentNullException("The selector is not valid for the specified index type.", "selector");

			return tableNumbers[selector];
		}
		public static bool TryResolveSelector(
			CodedIndexType indexType,
			int selector,
			out MetadataTableNumber tableNumber
			)
		{
			MetadataTableNumber[] tableNumbers = GetTableNumbers(indexType);
			bool isValid = (selector < tableNumbers.Length);
			if (isValid)
				tableNumber = tableNumbers[selector];
			else
				tableNumber = (MetadataTableNumber)(-1);

			return isValid;
		}

		public static int GetSelectorBitCount(CodedIndexType indexType)
		{
			return Log2(GetTableNumbers(indexType).Length - 1);
		}

		public MetadataTableNumber TableNumber { get { return TableFromToken(this.Token); } }
		public int RowNumber { get { return RowNumberFromToken(this.Token); } }

		public static CodedIndex Parse(int value, CodedIndexType indexType)
		{
			int selectorBits = GetSelectorBitCount(indexType);

			int tableSelector = value & ((1 << selectorBits) - 1);
			int rowNumber = value >> selectorBits;
			MetadataTableNumber tableNumber = ResolveSelector(indexType, tableSelector);

			return new CodedIndex(tableNumber, rowNumber);
		}
		public static bool TryParseRawValue(int value, CodedIndexType indexType, out CodedIndex index)
		{
			int selectorBits = GetSelectorBitCount(indexType);

			int tableSelector = value & ((1 << selectorBits) - 1);
			index = new CodedIndex();
			MetadataTableNumber tableNumber;
			bool isValid = TryResolveSelector(indexType, tableSelector, out tableNumber);
			if (isValid)
			{
				int rowNumber = value >> selectorBits;
				index = new CodedIndex(tableNumber, rowNumber);
			}

			return isValid;
		}

		public static CodedIndex FromToken(int token)
		{
			return new CodedIndex(token);
		}

		public CodedIndex(
			MetadataTableNumber tableNumber,
			int rowNumber
			)
		{
			this.Token = TokenFromTableRow(tableNumber, rowNumber);

			//this.TableNumber = tableNumber;
			//this.RowNumber = rowNumber;
		}
		public CodedIndex(int token)
		{
			this.Token = token;
		}

		public static int TokenFromTableRow(
			MetadataTableNumber tableNumber,
			int rowNumber
			)
		{
			return ((int)tableNumber << 24) | rowNumber;
		}

		public static MetadataTableNumber TableFromToken(int token)
		{
			return (MetadataTableNumber)(token >> 24);
		}
		public static int RowNumberFromToken(int token)
		{
			return (token & 0x00FFFFFF);
		}
		public static bool IsNullToken(int token)
		{
			return RowNumberFromToken(token) == 0;
		}
		public static bool IsCanonicalToken(int token)
		{
			return !IsReferenceToken(token);
			//MetadataTableNumber table=TableFromToken(token);

			//bool isCanonical = false;
			//switch (table)
			//{
			//    case MetadataTableNumber.TypeDef:
			//    case MetadataTableNumber.MethodDef:
			//    case MetadataTableNumber.Field:
			//    case MetadataTableNumber.Property:
			//    case MetadataTableNumber.Event:
			//        isCanonical = true;
			//        break;
			//}

			//return isCanonical;
		}
		public static bool IsReferenceToken(int token)
		{
			MetadataTableNumber table = TableFromToken(token);

			bool isReference = false;
			switch (table)
			{
				case MetadataTableNumber.AssemblyRef:
				case MetadataTableNumber.ModuleRef:
				case MetadataTableNumber.TypeRef:
				case MetadataTableNumber.TypeSpec:
				case MetadataTableNumber.MemberRef:
				case MetadataTableNumber.MethodSpec:
					isReference = true;
					break;
			}

			return isReference;
		}
		public static bool IsGenericParamToken(int token)
		{
			MetadataTableNumber table = TableFromToken(token);

			bool isGenericParam = false;
			switch (table)
			{
				case MetadataTableNumber.GenericParam:
					isGenericParam = true;
					break;
			}

			return isGenericParam;
		}

		//public int Token { get { return (int)this.TableNumber << 24 | this.RowNumber; } }
		public readonly int Token;

		public bool IsNull
		{
			get { return this.RowNumber == 0; }
		}
		public bool IsEmpty
		{
			get
			{
				return (this.TableNumber == 0) && (this.RowNumber == 0);
			}
		}
		public override bool Equals(object obj)
		{
			if (!(obj is CodedIndex)) return false;
			CodedIndex other = (CodedIndex)obj;

			return this.Equals(other);
		}
		public bool Equals(CodedIndex other)
		{
			bool equals = other.Token == this.Token;
			return equals;
		}
		public override int GetHashCode()
		{
			return (int)this.TableNumber ^ this.RowNumber;
		}

		public override string ToString()
		{
			return string.Format("{0}: {1}", this.TableNumber, this.RowNumber);
		}
	}
}
